/**
 * Copyright (C) 2008 University of Pittsburgh
 * 
 * 
 * This file is part of Open EpiCenter
 * 
 *     Open EpiCenter is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 * 
 *     Open EpiCenter is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 * 
 *     You should have received a copy of the GNU General Public License
 *     along with Open EpiCenter.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * 
 *   
 */
package com.hmsinc.epicenter.model.provider;

import static javax.persistence.GenerationType.AUTO;

import java.util.HashSet;
import java.util.Set;

import javax.persistence.Basic;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlID;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlTransient;
import javax.xml.bind.annotation.XmlType;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;

import org.apache.commons.lang.builder.ToStringBuilder;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.CollectionOfElements;
import org.hibernate.annotations.ForeignKey;
import org.hibernate.annotations.GenericGenerator;
import org.hibernate.annotations.Parameter;
import org.hibernate.annotations.Type;
import org.joda.time.DateTime;

import com.hmsinc.epicenter.model.analysis.QueryableAttribute;
import com.hmsinc.epicenter.model.health.Patient;
import com.hmsinc.epicenter.model.util.InvalidZipcodeException;
import com.hmsinc.epicenter.model.util.ModelUtils;
import com.vividsolutions.jts.geom.Geometry;

/**
 * Hospital generated by hbm2java
 */
@Entity
@Table(name = "FACILITY")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@org.hibernate.annotations.Table(appliesTo = "FACILITY", indexes = { @org.hibernate.annotations.Index(name = "IDX_FACILITY_1", columnNames = "ZIPCODE")})
@NamedQueries( { @NamedQuery(name = "getFacilityByIdentifier", query = "from Facility where identifier = :identifier"),
	@NamedQuery(name = "getFacilitiesInGeography", query = "from Facility f where within(f.geometry, :geo) = 'TRUE'")})
@XmlType(name="Facility", namespace = "http://epicenter.hmsinc.com/model")
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="facility", namespace = "http://epicenter.hmsinc.com/model")
public class Facility implements ProviderObject, QueryableAttribute {

	/**
	 * 
	 */
	private static final long serialVersionUID = -2190134343484598447L;

	private Long id;

	private String identifier;

	private String name;

	private String address1;

	private String address2;

	private String city;

	private String state;

	private String zipcode;

	private String timezone;
	
	private String reportingTimezone;
	
	private DateTime liveDate;

	private Geometry geometry;
	
	private Set<DataConnection> dataConnections = new HashSet<DataConnection>();

	private Set<FacilityType> types = new HashSet<FacilityType>();
	
	private Set<FacilityMilestone> milestones = new HashSet<FacilityMilestone>();
	
	private Set<Facility> affiliations = new HashSet<Facility>();
	
	private Set<Note> notes = new HashSet<Note>();
	
	private Set<Contact> contacts = new HashSet<Contact>();
	
	private Set<Patient> patients = new HashSet<Patient>();
	
	private Set<FacilityDUA> duas;
	
	// Constructors

	/** default constructor */
	public Facility() {
	}

	/**
	 * @param identifier
	 */
	public Facility(String identifier) {
		super();
		this.identifier = identifier;
	}

	/**
	 * @param id
	 * @param identifier
	 * @param name
	 * @param address1
	 * @param address2
	 * @param city
	 * @param state
	 * @param zipcode
	 * @param liveDate
	 * @param dataConnections
	 */
	public Facility(Long id, String identifier, String name, String address1, String address2, String city, String state, String zipcode,
			DateTime liveDate, Set<DataConnection> dataConnections, Set<Patient> patients) {
		super();
		this.id = id;
		this.identifier = identifier;
		this.name = name;
		this.address1 = address1;
		this.address2 = address2;
		this.city = city;
		this.state = state;
		this.zipcode = zipcode;
		this.liveDate = liveDate;
		this.dataConnections = dataConnections;
		this.patients = patients;
	}

	// Property accessors
	@Id
	@Column(name = "ID", nullable = false, insertable = true, updatable = true)
	@GenericGenerator(name = "generator", strategy = "native", parameters = { @Parameter(name = "sequence", value = "SEQ_FACILITY") })
	@GeneratedValue(strategy = AUTO, generator = "generator")
	@XmlTransient
	public Long getId() {
		return this.id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	@XmlAttribute(required = true, name = "id")
	@XmlID
	@Transient
	public String getIdRef() {
		return this.id.toString();
	}
	
	public void setIdRef(String id) {
		this.id = Long.valueOf(id);
	}
	
	@XmlElement(required = true, namespace = "http://epicenter.hmsinc.com/model")
	@Column(name = "IDENTIFIER", unique = true, nullable = true, insertable = true, updatable = true, length = 20)
	public String getIdentifier() {
		return this.identifier;
	}

	public void setIdentifier(String identifier) {
		this.identifier = identifier;
	}

	@XmlElement(namespace = "http://epicenter.hmsinc.com/model")
	@Column(name = "NAME", unique = false, nullable = true, insertable = true, updatable = true, length = 80)
	public String getName() {
		return this.name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@XmlElement(namespace = "http://epicenter.hmsinc.com/model")
	@Column(name = "ADDRESS1", unique = false, nullable = true, insertable = true, updatable = true, length = 40)
	public String getAddress1() {
		return this.address1;
	}

	public void setAddress1(String address1) {
		this.address1 = address1;
	}

	@XmlElement( namespace = "http://epicenter.hmsinc.com/model")
	@Column(name = "ADDRESS2", unique = false, nullable = true, insertable = true, updatable = true, length = 40)
	public String getAddress2() {
		return this.address2;
	}

	public void setAddress2(String address2) {
		this.address2 = address2;
	}

	@XmlElement(namespace = "http://epicenter.hmsinc.com/model")
	@Column(name = "CITY", unique = false, nullable = true, insertable = true, updatable = true, length = 25)
	public String getCity() {
		return this.city;
	}

	public void setCity(String city) {
		this.city = city;
	}

	@XmlElement(namespace = "http://epicenter.hmsinc.com/model")
	@Column(name = "STATE", unique = false, nullable = true, insertable = true, updatable = true, length = 2)
	public String getState() {
		return this.state;
	}

	public void setState(String state) {
		this.state = state;
	}

	@XmlElement(namespace = "http://epicenter.hmsinc.com/model")
	@Column(name = "ZIPCODE", unique = false, nullable = true, insertable = true, updatable = true, length = 5)
	public String getZipcode() {
		return this.zipcode;
	}

	public void setZipcode(String zipcode) throws InvalidZipcodeException {
		this.zipcode = ModelUtils.validateZipcode(zipcode);
	}

	/**
	 * @return the timezone
	 */
	@Column(name = "TIMEZONE", unique = false, nullable = true, insertable = true, updatable = true, length = 6)
	public String getTimezone() {
		return timezone;
	}

	/**
	 * @param timezone the timezone to set
	 */
	public void setTimezone(String timezone) {
		this.timezone = timezone;
	}

	/**
	 * @return the reportingTimezone
	 */
	@Column(name = "REPORTING_TIMEZONE", unique = false, nullable = true, insertable = true, updatable = true, length = 6)
	public String getReportingTimezone() {
		return reportingTimezone;
	}

	/**
	 * @param reportingTimezone the reportingTimezone to set
	 */
	public void setReportingTimezone(String reportingTimezone) {
		this.reportingTimezone = reportingTimezone;
	}

	@XmlElement(namespace = "http://epicenter.hmsinc.com/model")
	@Type(type = "joda")
	@Column(name = "LIVE_DATE", unique = false, nullable = true, insertable = true, updatable = true, length = 7)
	public DateTime getLiveDate() {
		return this.liveDate;
	}

	public void setLiveDate(DateTime liveDate) {
		this.liveDate = liveDate;
	}

	/**
	 * @return the dataConnections
	 */
	@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "facilities", targetEntity = DataConnection.class)
	@ForeignKey(name = "FK_DATA_CONN_FACILITY_1", inverseName = "FK_DATA_CONN_FACILITY_2")
	@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	public Set<DataConnection> getDataConnections() {
		return dataConnections;
	}

	/**
	 * @param dataConnections
	 *            the dataConnections to set
	 */
	public void setDataConnections(Set<DataConnection> dataConnections) {
		this.dataConnections = dataConnections;
	}

	/**
	 * @return the types
	 */
	@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, mappedBy = "facilities", targetEntity = FacilityType.class)
	@ForeignKey(name = "FK_FACILITY_TYPE_FACILITY_1", inverseName = "FK_FACILITY_TYPE_FACILITY_2")
	@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	public Set<FacilityType> getTypes() {
		return types;
	}

	/**
	 * @param types the types to set
	 */
	public void setTypes(Set<FacilityType> types) {
		this.types = types;
	}

	/**
	 * @return the affiliations
	 */
	@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY, targetEntity = Facility.class)
	@JoinTable(name = "FACILITY_AFFILIATION", joinColumns = { @JoinColumn(name = "ID_FACILITY") }, inverseJoinColumns = { @JoinColumn(name = "ID_FACILITY_AFFILIATION") })
	@ForeignKey(name = "FK_FACILITY_AFFILIATION_1", inverseName = "FK_FACILITY_AFFILIATION_2")
	@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	public Set<Facility> getAffiliations() {
		return affiliations;
	}

	/**
	 * @param affiliations the affiliations to set
	 */
	public void setAffiliations(Set<Facility> affiliations) {
		this.affiliations = affiliations;
	}

	/**
	 * @return the milestones
	 */
	@CollectionOfElements
	@JoinTable(name = "FACILITY_MILESTONE", joinColumns = { @JoinColumn(name = "ID_FACILITY" )})
	@ForeignKey(name = "FK_FACILITY_MILESTONE_1")
	public Set<FacilityMilestone> getMilestones() {
		return milestones;
	}

	/**
	 * @param milestones the milestones to set
	 */
	public void setMilestones(Set<FacilityMilestone> milestones) {
		this.milestones = milestones;
	}

	/**
	 * @return the notes
	 */
	@OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, mappedBy = "facility")
	public Set<Note> getNotes() {
		return notes;
	}

	/**
	 * @param notes the notes to set
	 */
	public void setNotes(Set<Note> notes) {
		this.notes = notes;
	}

	/**
	 * @return the contacts
	 */
	@ManyToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY, targetEntity = Contact.class)
	@JoinTable(name = "FACILITY_CONTACT", joinColumns = { @JoinColumn(name = "ID_FACILITY") }, inverseJoinColumns = { @JoinColumn(name = "ID_CONTACT") })
	@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	public Set<Contact> getContacts() {
		return contacts;
	}

	/**
	 * @param contacts the contacts to set
	 */
	public void setContacts(Set<Contact> contacts) {
		this.contacts = contacts;
	}

	/**
	 * @return the patients
	 */
	@OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, mappedBy = "facility")
	@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
	public Set<Patient> getPatients() {
		return patients;
	}

	/**
	 * @param patients
	 *            the patients to set
	 */
	public void setPatients(Set<Patient> patients) {
		this.patients = patients;
	}

	/**
	 * The backing type of this column is determined by the Hibernate Spatial dialect.
	 * 
	 * In order for this property to be "lazy", compile-time bytecode instrumentation is
	 * required.  The Maven build should do this automatically.
	 * 
	 * @see com.hmsinc.model.warehouse.geo.Geography#getGeometry()
	 */
	@XmlElement(required = true, namespace = "http://epicenter.hmsinc.com/model")
	@Basic(fetch = FetchType.LAZY)
	@Column(name = "GEOMETRY", unique = false, nullable = true, insertable = true, updatable = true)
	@org.hibernate.annotations.Type(type = "geometry")
	@XmlJavaTypeAdapter(com.hmsinc.hibernate.spatial.jaxb2.GeometryTypeAdapter.class)
	public Geometry getGeometry() {
		return geometry;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.hmsinc.model.warehouse.geo.Geography#setGeometry(com.vividsolutions.jts.geom.Geometry)
	 */
	public void setGeometry(Geometry geometry) {
		this.geometry = geometry;
	}
	
	/**
	 * @return the duas
	 */
	@OneToMany(cascade = { CascadeType.PERSIST, CascadeType.MERGE }, fetch = FetchType.LAZY, mappedBy = "facility")
	public Set<FacilityDUA> getDuas() {
		return duas;
	}

	/**
	 * @param duas the duas to set
	 */
	public void setDuas(Set<FacilityDUA> duas) {
		this.duas = duas;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return new ToStringBuilder(this).append("id", id).append("identifier", identifier).append("name", name).append("address1", address1)
			.append("address2", address2).append("city", city).append("state", state).append("zipcode", zipcode).toString();
	}
}
